﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using FgoDataCrawler.Entity;
using FgoDataCrawler.Entity.AppMedia;
using FgoDataCrawler.Utility;
using HtmlAgilityPack;
using NLog;
using NSoup.Nodes;

namespace FgoDataCrawler.Crawler
{
    public class AppMediaCrawler
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        private const string BASE_URL = "https://appmedia.jp/fategrandorder/";
        private const string DEFAULT_CACHE_DIR = "D:\\temp\\AppMedia";

        private const string START_TAG = "一覧";
        private const string END_TAG = "育成難易度について";

        private const string AVATAR_URL_REGEX = @".*/uploads/.*/(\w+)/\.jpg";

        private CachedHttpClient chClient { get; set; }

        // private Dictionary<int, int> IdMapper = new Dictionary<int, int>();

        public AppMediaCrawler()
        {
            chClient = new CachedHttpClient(DEFAULT_CACHE_DIR);
        }

        public IList<ServantInfo> FetchServents()
        {
            List<ServantInfo> servants = new List<ServantInfo>();

            // Saber Archer Lancer, Extra,
            // Rider, Caster, Assassin, Berserker
            List<KeyValuePair<int,string>> posts = new List<KeyValuePair<int, string>>();

            posts.Add(new KeyValuePair<int, string>(767665, "Saber"));
            posts.Add(new KeyValuePair<int, string>(792505, "Archer"));
            posts.Add(new KeyValuePair<int, string>(792759, "Lancer"));
            posts.Add(new KeyValuePair<int, string>(792960, "Extra"));
            posts.Add(new KeyValuePair<int, string>(792912, "Rider"));
            posts.Add(new KeyValuePair<int, string>(792262, "Caster"));
            posts.Add(new KeyValuePair<int, string>(792470, "Assassin"));
            posts.Add(new KeyValuePair<int, string>(792744, "Berserker"));

            // int[] postIds =
            // {
            //     767665, 792505, 792759, 792960,
            //     792912, 792262, 792470, 792744
            // };
            // int[] postIds = { 792960 }; // Extra only

            foreach (var post in posts)
            {
                IList<ServantInfo> servantsInPage = FetchServantsInPage(post.Key, post.Value);
                servants.AddRange(servantsInPage);
            }

            return servants;
        }

        public IList<ServantInfo> FetchServantsInPage(int postId, string className)
        {
            DateTime startTime;
            TimeSpan timeCost;

            string url = BASE_URL + postId;
            logger.Info("开始抓取{0}", url);
            startTime = DateTime.Now;
            var content = chClient.GetAsString(postId+".html", url, Encoding.UTF8);
            timeCost = DateTime.Now - startTime;
            logger.Info("抓取完成，耗时{0}ms", timeCost.Milliseconds);

            var doc = new HtmlDocument();
            logger.Info("开始解析页面");
            startTime = DateTime.Now;
            doc.LoadHtml(content);
            timeCost = DateTime.Now - startTime;
            logger.Info("页面解析完成，耗时{0}ms", timeCost.Milliseconds);

            var xpath = "//div[contains(@class,'post-content')]";
            var postContent = doc.DocumentNode.SelectSingleNode(xpath);

            HtmlNode startNode = null;
            foreach (var child in postContent.ChildNodes)
            {
                if (child.Name == "h2" && child.InnerText.Trim().EndsWith(START_TAG))
                {
                    startNode = child;
                    break;
                }
            }

            var currentNode = startNode.NextSibling;
            IList<ServantInfo> servants = new List<ServantInfo>();
            while (!(currentNode.Name == "h3" && currentNode.InnerText.Trim() == END_TAG))
            {
                if (currentNode.Name == "table")
                {
                    xpath = "./tbody/tr";
                    var trs = currentNode.SelectNodes(xpath);
                    for (int index = 1; index < trs.Count; index++)
                    {
                        var tr = trs[index];
                        var tds = tr.SelectNodes("./td");
                        if (tds.Count != 4)
                        {
                            logger.Warn("第{0}行列数有异常", index);
                            continue;
                        }
                        var tdAvatar = tds[0];
                        var imgNode = tdAvatar.SelectSingleNode(".//img");
                        var aNode = tdAvatar.SelectSingleNode(".//a");
                        string link = aNode.Attributes["href"].Value;
                        string appmediaId = link.Substring(link.LastIndexOf("/")+1);
                        string avatarSrc = imgNode.Attributes["src"].Value;
                        string servantName = tdAvatar.InnerText.Trim().Replace("\r", " ").Replace("\n", " ").Replace("&amp;", "&");  //.Replace("?", "・");
                        
                        logger.Info("{0}\t{1}", Int32.Parse(appmediaId), servantName);
                        ServantInfo servant = new ServantInfo();
                        servant.JapaneseName = servantName;
                        servant.AppMediaId = Int32.Parse(appmediaId);
                        servant.Class = className;
                        servants.Add(servant);
                    }
                }
                currentNode = currentNode.NextSibling;
            }

            return servants;
        }

        public LeaderBoard FetchLeaderBoard(int rare)
        {
            int postId = 0;
            switch (rare)
            {
                case 5:
                    postId = 96261;
                    break;
                case 4:
                    postId = 93558;
                    break;
                default:
                    return null;
            }

            DateTime startTime;
            TimeSpan timeCost;
            string url = BASE_URL + postId;
            logger.Info("开始抓取{0}", url);
            startTime = DateTime.Now;
            var content = chClient.GetAsString(postId + ".html", url, Encoding.UTF8);
            timeCost = DateTime.Now - startTime;
            logger.Info("抓取完成，耗时{0}ms", timeCost.Milliseconds);

            var doc = new HtmlDocument();
            logger.Info("开始解析页面");
            startTime = DateTime.Now;
            doc.LoadHtml(content);
            timeCost = DateTime.Now - startTime;
            logger.Info("页面解析完成，耗时{0}ms", timeCost.Milliseconds);

            string xpath = null;
            HtmlNode h2 = null;
            HtmlNode table = null;
            
            if (rare == 5)
            {
                // star 5
                xpath = "//div[contains(@class,'post-content')]/h2[contains(@id,'最強')]";
                // table = h2.NextSibling.NextSibling.NextSibling;
                // if (table.Name != "table")
                // {
                //     logger.Warn("未在指定节点找到table，页面发生变化");
                //     return null;
                // }
                h2 = doc.DocumentNode.SelectSingleNode(xpath);
                table = FindLeaderBoardTableSSR(h2);
                return ParseLeaderBoardTableSSR(table);
            }
            if (rare == 4)
            {
                // star 4
                xpath = "//div[contains(@class,'post-content')]/h2[contains(@id,'2')]";
                // table = h2.NextSibling.NextSibling.NextSibling.NextSibling;
                // if (table.Name != "table")
                // {
                //     logger.Warn("未在指定节点找到table，页面发生变化");
                //     return null;
                // }
                h2 = doc.DocumentNode.SelectSingleNode(xpath);
                table = FindLeaderBoardTableSR(h2);
                return ParseLeaderBoardTableSR(table);
            }
            return null;
        }

        public HtmlNode FindLeaderBoardTableSSR(HtmlNode h2Node)
        {
            HtmlNode currentNode = h2Node.NextSibling;
            bool findout = false;
            while (!findout)
            {
                if (currentNode == null) break;
                if (currentNode.Name == "table")
                {
                    var row1st = currentNode.SelectSingleNode(".//tr");
                    var cellsInRow1st = row1st.SelectNodes("./th");
                    if (cellsInRow1st.Count == 3)
                    {
                        if (cellsInRow1st[0].InnerText.Trim() == "サーヴァント"
                            && cellsInRow1st[1].InnerText.Trim() == "クラス"
                            && cellsInRow1st[2].InnerText.Trim() == "評価点数")
                        {
                            findout = true;
                            break;
                        }
                    }
                }
                currentNode = currentNode.NextSibling;
            }
            if (findout) return currentNode;
            return null;
        }

        public HtmlNode FindLeaderBoardTableSR(HtmlNode h2Node)
        {
            HtmlNode currentNode = h2Node.NextSibling;
            bool findout = false;
            while (!findout)
            {
                if (currentNode == null) break;
                if (currentNode.Name == "table")
                {
                    // サーヴァント	評価	周回	高難度	唯一性
                    var row1st = currentNode.SelectSingleNode(".//tr");
                    var cellsInRow1st = row1st.SelectNodes("./th");
                    if (cellsInRow1st.Count == 5)
                    {
                        if (cellsInRow1st[0].InnerText.Trim() == "サーヴァント"
                            && cellsInRow1st[1].InnerText.Trim() == "評価"
                            && cellsInRow1st[2].InnerText.Trim() == "周回"
                            && cellsInRow1st[3].InnerText.Trim() == "高難度"
                            && cellsInRow1st[4].InnerText.Trim() == "唯一性")
                        {
                            findout = true;
                            break;
                        }
                    }
                }
                currentNode = currentNode.NextSibling;
            }
            if (findout) return currentNode;
            return null;
        }

        private LeaderBoard ParseLeaderBoardTableSSR(HtmlNode table)
        {
            LeaderBoard lboard = new LeaderBoard();
            lboard.Rare = 5;
            lboard.Name = "星5サーヴァント最強ランキング";

            var trs = table.SelectNodes(".//tr");
            var currentRank = "";
            for (var line = 1; line < trs.Count; line++)
            {
                var tr = trs[line];
                var ths = tr.SelectNodes("./th");
                if (ths != null && ths.Count == 1)
                {
                    var th = ths[0];
                    var rankName = th.InnerText.Trim();
                    switch (rankName)
                    {
                        case "▼SSランク▼":
                            currentRank = "SS";
                            break;
                        case "▼S+ランク▼":
                            currentRank = "S+";
                            break;
                        case "▼Sランク▼":
                            currentRank = "S";
                            break;
                        case "▼Aランク▼":
                            currentRank = "A";
                            break;
                        case "▼Bランク▼":
                            currentRank = "B";
                            break;
                        case "▼Cランク▼":
                            currentRank = "C";
                            break;
                    }
                    continue;
                }
                var tds = tr.SelectNodes("./td");
                if (tds != null && tds.Count == 3)
                {
                    var link = tds[0].SelectSingleNode(".//a");

                    ServantMark servantMark = new ServantMark();
                    servantMark.Rank = currentRank;
                    servantMark.Name = link.InnerText.Trim();
                    servantMark.DetailUrl = link.Attributes["href"].Value.Trim();

                    servantMark.Score = tds[2].InnerText.Trim();

                    lboard.Marks.Add(servantMark);
                }
            }
            return lboard;
        }

        private LeaderBoard ParseLeaderBoardTableSR(HtmlNode table)
        {
            LeaderBoard lboard = new LeaderBoard();
            lboard.Rare = 4;
            lboard.Name = "星4サーヴァント最強ランキング";

            var trs = table.SelectNodes(".//tr");
            var currentRank = "";
            for (var line = 1; line < trs.Count; line++)
            {
                var tr = trs[line];
                var ths = tr.SelectNodes("./th");
                if (ths != null && ths.Count == 1)
                {
                    var th = ths[0];
                    var rankName = th.InnerText.Trim();
                    switch (rankName)
                    {
                        case "▼SSランク▼":
                            currentRank = "SS";
                            break;
                        case "▼S+ランク▼":
                            currentRank = "S+";
                            break;
                        case "▼Sランク▼":
                            currentRank = "S";
                            break;
                        case "▼Aランク▼":
                            currentRank = "A";
                            break;
                        case "▼Bランク▼":
                            currentRank = "B";
                            break;
                        case "▼Cランク▼":
                            currentRank = "C";
                            break;
                    }
                    continue;
                }
                var tds = tr.SelectNodes("./td");
                if (tds != null && tds.Count == 5)
                {
                    var link = tds[0].SelectSingleNode(".//a");

                    ServantMark servantMark = new ServantMark();
                    servantMark.Rank = currentRank;
                    servantMark.Name = link.InnerText.Trim();
                    servantMark.DetailUrl = link.Attributes["href"].Value.Trim();

                    servantMark.Score = tds[1].InnerText.Trim();

                    lboard.Marks.Add(servantMark);
                }
            }

            return lboard;
        }
    }
}
